ESP32-P4 骑行终端项目深挖面试准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# ESP32-P4 骑行终端项目深挖面试准备

> 项目源码:`/home/weixun/esp32p4/lvgl9/p4-idf6_co5300-mipi_lvgl-common-demo`
>
> 目标岗位:韵音科技嵌入式开发实习生。
>
> 本文依据项目源码、项目文档和 Git 历史整理。第三方组件集成、算法原理、自主实现和实机验证必须分开表述,不把调用开源库说成自己实现算法。

---

## 1. 先纠正项目定位

这不是“ESP32 无线透传小项目”,而是一套基于 ESP32-P4 的圆屏骑行终端原型。项目以 ESP-IDF 和 FreeRTOS 为系统基础,集成 LVGL、MIPI DSI 显示、触摸、电源管理、BLE、离线地图、路线导航、运动数据模型、IMU/磁力计融合和语音链路。

### 硬件与软件栈

| 模块 | 项目实际实现 |
|---|---|
| 主控 | ESP32-P4 |
| 无线协处理器 | ESP32-C6 |
| P4-C6 链路 | ESP-Hosted over SDIO |
| RTOS | ESP-IDF FreeRTOS |
| UI | LVGL 9.5,466×466 CO5300 MIPI DSI 圆屏 |
| 触摸 | CST9217 |
| PMIC | AXP2101 |
| BLE | P4 上 NimBLE Host/GATT Server,C6 提供 Controller |
| 存储 | 16 MB Flash、PSRAM、NVS、SD 卡 |
| 传感器 | LSM6DS3TR-C + QMC5883P |
| 姿态算法 | Mahony 9 轴融合、四元数、陀螺仪零偏和磁力计校准 |
| 音频输入 | ICS43434,I2S,16 kHz,32-bit slot 转 PCM16 mono |
| 音频输出 | MAX98357A,I2S |
| 编解码 | OPUS,16 kHz mono,60 ms/960 samples |
| 地图 | SD 卡分块离线矢量地图、路线预览和导航跟随 |

### Git 可核验的个人工作

仓库共 18 个提交,作者一致。关键提交包括:

- `3a8d4b7`:加入 IMU 融合并优化转向;
- `b10c08f`:修复定位和导航视觉路线抖动;
- `278054a`:修复停车速度、速度误差、提前到达和终点不触发;
- `2f2652a`:加入 SD 卡路线、路线下发和导航视觉;
- `3b80c36`:修复 Xiaozhi 运行时 LVGL 卡顿;
- `afb4879`:加入本地语音导航播报;
- `4b1cd76`:修复数据播报和数据溢出;
- `fc3c769`:修复上下拉菜单卡顿。

这些提交可以作为面试证据,但仍要能解释代码、调试过程和验证方式。

---

## 2. 已确认事实与表达边界

| 内容 | 当前证据 | 面试前动作 |
|---|---|---|
| 项目周期 | 用户确认从 5 月持续到 8 月,具体年份未填写 | 口述可说“从 5 月做到 8 月”;简历需补准确年份 |
| 团队与个人职责 | 三人团队;用户确认仓库中的嵌入式软件实现由本人完成 | 可以说“我独立负责嵌入式端软件”,但仍要说明另外两人的真实职责 |
| PCB | 用户确认 PCB 由本人绘制 | 准备原理图分块、电源、接口、层数和一次硬件问题,防止只停留在一句“我画的” |
| 2~2048 路线点 | 协议文档和宏 `APP_BLE_ROUTE_MAX_POINTS=2048` 可核验 | 可以讲 |
| CRC32、route_id、offset、Route Status | 源码和协议文档可核验 | 可以讲 |
| 40 ms 导航状态、100 ms 地图绘制下限 | 项目文档和代码设计可核验 | 说“刷新周期/下限”,不要说成 CPU 实测耗时 |
| Mahony 9 轴融合 | 源码和提交可核验 | 可以讲,但需掌握公式思想 |
| 静止航向 30 秒变化不超过 5° | 只是项目验收线,没有保留测试记录 | 不能说“实测达到”,只可说这是计划验证的指标 |
| 音频延迟和连续运行时长 | 没有保留测试记录 | 不报数字,只讲链路、参数、故障和验证方法 |
| 导航抖动优化效果 | 有代码和提交,但没有正式对比测试 | 可说“针对抖动修改过算法并做过运行观察”,不能声称量化提升 |
| SRAM 启动断言 | 有排查文档,但本人已忘记最后修改和复测结果 | 只作为排查思路备用题,补回证据前不要包装成闭环成果 |
| GNSS 是否已稳定工作 | 文档明确板载 GNSS 曾不稳定,手机定位作为有效来源 | 不要说板载 GNSS 已完全打通 |
| 姿态航向是否已接入地图 | 文档写明尚未写入统一运动模型 | 不要说融合航向已驱动导航 |
| 量产经历 | 项目为可运行原型 | 说做了工程化设计,不说已量产 |

团队表述要特别注意。不要直接说“三个人,但所有东西都是我做的”,这会让面试官立即追问另外两个人的作用,也可能被理解为缺少协作。当前最稳妥且符合你确认信息的说法是:

> 这是一个三人项目,我独立负责嵌入式端软件实现和 PCB 绘制。软件部分包括系统架构、驱动与任务、界面、通信、地图导航、姿态和音频链路。其他两位成员的职责我会按实际分工说明。

最后一句仍需补上另外两位成员的真实职责。面试官可能沿着需求分析、结构设计、焊接装配、测试、文档和演示继续追问分工,必须按实际情况回答。

---

## 3. 三种长度的项目介绍

### 3.1 30 秒版本

> 我在一个三人项目中独立负责嵌入式端软件,并绘制了项目 PCB。终端基于 ESP32-P4、ESP-IDF、FreeRTOS 和 LVGL,集成圆形 MIPI 屏、BLE、离线地图、路线导航、IMU/磁力计融合和语音链路。比较有挑战的是协调界面、无线和实时任务,以及完成传感器校准、Mahony 姿态融合和分包路线协议。

### 3.2 两分钟版本

> 这是一个从 5 月持续到 8 月的三人项目,我独立负责嵌入式端软件实现,同时绘制了项目 PCB。终端面向骑行和山地运动,主控是 ESP32-P4,无线由 ESP32-C6 协处理器提供,两者通过 ESP-Hosted SDIO 通信。界面使用 LVGL 9.5 驱动 466×466 的 MIPI 圆屏。软件按板级驱动、数据模型、通信、地图导航和 UI 分层,实时运动数据通过统一快照提供给地图、数据页、BLE、历史记录和语音模块,避免各页面各自维护一套状态。
>
> 通信方面,我在 P4 上使用 NimBLE Host,将手机下发的完整路线设计成 BEGIN、DATA、COMMIT 和状态查询流程,支持 2 到 2048 个点。协议包含 route_id、顺序 offset、长度和 CRC32,接收缓冲与解析任务放在 PSRAM,BLE 回调不直接做地图计算或操作 LVGL。
>
> 对韵音岗位最相关的是姿态和音频部分。我接入了 LSM6DS3TR-C 和 QMC5883P,用 GPIO 中断通知独立任务,在任务里完成坐标变换、陀螺仪零偏、磁力计硬铁/软铁近似校准和 Mahony 9 轴融合,通过四元数输出横滚、俯仰和磁航向。音频链路使用 ICS43434 采集 16 kHz PCM,调用 OPUS 库编码后上传,下行 OPUS 解码后通过 MAX98357A 播放。我还针对语音运行时 LVGL 卡顿调整过任务和数据流。当前没有保留延迟、长稳和导航优化的量化数据,所以面试中我会重点讲实现、问题定位方法和后续测试方案,不虚报指标。

### 3.3 五分钟版本结构

不要连续背稿,按下面顺序画图讲:

1. 产品目标和硬件平台;
2. 系统分层和任务关系;
3. 选一条核心数据流;
4. 重点讲一个算法模块;
5. 重点讲一个疑难 Bug;
6. 给出验证结果和当前边界;
7. 说明下一步改进。

---

## 4. 系统架构怎么画

```text
手机 / 小程序 / 服务端
| BLE / Wi-Fi
v
ESP32-C6 无线 Controller
| ESP-Hosted SDIO
v
+----------------------------------------------------------------+
| ESP32-P4 |
| |
| Board/HAL |
| PMIC Touch LCD SD I2S IMU Magnetometer Button |
| | | | | | | | | |
| +------+-----+----+----+----+--------+----------+ |
| | |
| Services/Tasks v |
| BLE task Route task Attitude task Audio task Wi-Fi task |
| | | | | | |
| +---------+-----------+--------------+----------+ |
| | |
| Models v |
| motion snapshot / route snapshot / weather / history / time |
| | |
| UI v |
| LVGL page manager -> cycling / MTB / map / history / settings |
+----------------------------------------------------------------+

为什么要使用快照模型?

传感器、BLE 和导航任务的更新频率与 UI 刷新频率不同,而且 LVGL 不是任意任务都能安全调用。底层只更新受保护的数据模型,UI 定时读取快照并局部刷新。这样可以避免底层任务直接操作 UI、减少锁持有时间,也让 BLE、地图、历史和语音读取同一份运动状态。

追问:快照是否完全无锁?

不是。不同模型使用临界区或 mutex 保护写入和复制。快照的价值是把锁内操作限制为固定大小状态复制,复杂计算和绘制放在锁外。包含动态数组指针的快照还要处理生命周期和版本,不能只复制裸指针后让生产者立即释放。


5. 核心故事一:IMU 与磁力计融合

这是韵音面试应优先主讲的模块。

5.1 需求和硬件限制

  • LSM6DS3TR-C:加速度计 ±2g、陀螺仪 ±500 dps、52 Hz;
  • QMC5883P:磁场 ±8G、50 Hz;
  • 两颗传感器共享 GPIO33/34 软件 I2C,目标 400 kHz;
  • GPIO30 接 IMU INT1;
  • 硬件 I2C 资源已被触摸和 PMIC 使用,LP I2C 引脚又不适配 GPIO33/34,因此实现独立软件 I2C;
  • 目标是输出四元数、roll/pitch/yaw、磁航向和运动状态,同时不能阻塞 UI。

5.2 数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LSM6DS3TR-C 52 Hz data ready
|
v
GPIO30 ISR:增加计数 + task notify
|
v
board_attitude task
-> 读取 accel/gyro
-> 读取 QMC5883P 最新磁场
-> 统一设备坐标系
-> 静止检测和 gyro bias
-> 应用 mag offset/scale
-> 初始化或更新 Mahony 四元数
-> 转换欧拉角和 heading
-> 临界区发布快照

5.3 为什么 ISR 不读取 I2C?

软件 I2C 包含 GPIO 翻转、ACK、重复起始、时钟拉伸等待和总线恢复,不适合放在 ISR 中。ISR 只做任务通知,任务再串行访问总线。这样可以限制中断时间,也避免 ISR 内阻塞、日志和复杂浮点计算。

5.4 为什么先做坐标变换?

PCB 安装方向与传感器封装坐标不同,项目统一为:

1
2
3
device X = sensor X
device Y = -sensor Y
device Z = -sensor Z

加速度、角速度和磁场必须先变换到同一个右手设备坐标系,再参与融合。如果不同传感器各自随意调轴,静止时可能看起来正常,但设备旋转后反馈方向会互相矛盾,偏航和姿态会漂移或跳变。

5.5 Mahony 算法怎么解释?

陀螺仪角速度通过积分更新姿态,短时响应好但会累积零偏;加速度在非剧烈运动时可提供重力方向,磁力计提供地磁方向。Mahony 根据当前四元数预测的重力/磁场方向与传感器测量方向之间的叉积得到误差,用比例项快速修正、积分项补偿长期偏差,再积分更新四元数并归一化。最后由四元数转换欧拉角。

不要说“Mahony 消除了所有漂移”。运动加速度、磁干扰、标定误差和参数选择仍会影响结果。

5.6 陀螺仪零偏如何做?

  • 启动后采集 104 个稳定样本,约 2 秒;
  • 加速度模长要求在 0.90~1.10g;
  • 角速度和样本变化不能超过阈值;
  • 检测到移动就清空窗口重新采集;
  • 对稳定样本求三轴均值,后续角速度先减 bias。

回答重点:为什么不能设备移动时直接求平均?因为真实旋转会被误认为零偏,后续所有姿态都会产生系统性误差。

5.7 磁力计校准如何做?

项目通过三方向旋转采集每轴 min/max:

1
2
3
4
offset = (max + min) / 2
radius = (max - min) / 2
average_radius = (rx + ry + rz) / 3
scale_axis = average_radius / radius_axis

这能近似补偿硬铁偏移和各轴尺度差异。少于 100 个有效样本或任一轴半径小于 10 uT 时拒绝保存。通过 30 秒静止检查后才把带 magic、版本和长度的参数写入 NVS。

边界要主动说:这种 min/max 椭球近似不能完整解决任意软铁非正交误差;更精确方案可以采集三维点云做椭球拟合和 3×3 校正矩阵。

5.8 为什么使用四元数而不是只存欧拉角?

四元数没有欧拉角在特定姿态下的万向节锁问题,连续旋转时数值更稳定,组合旋转也更方便。代价是需要保持单位长度,并在需要展示时转换成欧拉角。

5.9 如何验证?

  • 静止平放:加速度模长接近 1g、角速度接近 0;
  • 固定方向翻转:roll/pitch 正负方向一致;
  • 水平右转:heading 增加,左转减少;
  • 旋转一周:航向覆盖 0~360°且连续;
  • 校准后磁场轨迹中心接近零,各轴幅值接近;
  • 静止检查记录 0/10/20/30 秒航向,项目验收线为变化不超过 5°;
  • 断开磁力计:保留 6 轴姿态,heading_valid=false
  • 断开 IMU:姿态模块失败降级,但 UI/BLE 继续启动。

5.10 面试官可能深挖

  1. 为什么加速度无法长期确定 yaw?
  2. 磁力计为什么需要倾斜补偿?
  3. Mahony 的 Kp/Ki 如何调?
  4. dt 为什么要使用实际时间差并限制范围?
  5. 磁场异常时是否继续使用旧数据?
  6. 52 Hz 和 50 Hz 不同频如何融合?
  7. 软件 I2C 被任务抢占会怎样?
  8. 快照发布为什么需要临界区?
  9. 为什么只在 PASS 后写 NVS?
  10. 扬声器和磁吸结构为什么影响磁航向?

6. 核心故事二:PCM/OPUS 音频链路

6.1 数据链路

1
2
3
4
5
6
7
上行:ICS43434 -> I2S 32-bit right slot -> PCM16 mono
-> 静音门限 -> OPUS encode -> WebSocket -> 服务端

下行:WebSocket OPUS packet -> 队列 -> OPUS decode
-> PCM16 mono -> 饱和增益 -> stereo slots -> MAX98357A

本地播报:预生成 16 kHz PCM -> 2 KB PSRAM 分块读取 -> MAX98357A

6.2 参数

  • 采样率:16 kHz;
  • 单声道;
  • 帧长:60 ms,即 960 samples;
  • PCM:16 bit;
  • OPUS bitrate:16 kbps;
  • complexity:1;
  • DTX:开启;
  • 本地静音门限:平均绝对值 96;
  • 麦克风 raw/PCM/OPUS buffer 使用 internal 8-bit 内存;
  • 大任务栈和适合的对象根据能力放入 PSRAM。

6.3 为什么 60 ms 是 960 个样本?

1
16000 samples/s × 0.060 s = 960 samples

PCM16 mono 每帧原始数据为:

1
960 × 2 bytes = 1920 bytes

面试官可能继续问:60 ms 帧比 20 ms 帧延迟更高,但包频率和协议开销更低;需要在实时性、编码效率、网络和 CPU 之间权衡。

6.4 为什么回调只入队?

WebSocket/Xiaozhi 回调中不直接解码和播放,而是复制有界数据后入队,由 ai_audio 任务处理 OPUS 解码和 I2S。这样避免阻塞协议回调,隔离网络抖动和音频执行时间。队列满时必须定义丢弃策略并释放旧包内存,避免泄漏。

6.5 为什么要做静音门限和 DTX?

本地门限可以让明显静音帧不进入编码和发送,降低 CPU、网络和 ESP-Hosted SDIO 压力;OPUS DTX 则是编码器层对静音的进一步优化。固定门限可能在环境噪声变化时误判,更完善方案需要噪声底估计、迟滞和 hangover。

6.6 为什么扬声器播放时暂停上行?

当前设计在服务端 TTS 播放期间仍读取麦克风,但暂停上传,降低扬声器声音被麦克风再次上传形成回声的概率。这不是完整声学回声消除。完整 AEC 需要参考信号、时延对齐和自适应滤波。

6.7 第三方库边界

必须准确说:

  • OPUS 编解码算法来自 78/esp-opus/libopus,不是你自己实现;
  • esp_xiaozhi 提供聊天协议与传输能力;
  • 你的工作是组件选型、依赖冲突处理、任务/缓冲设计、I2S 接入、参数配置、生命周期和错误恢复;
  • 本地导航语音通过预生成 PCM 片段拼接播放,不是运行时 TTS 算法。

6.8 音频链路可能追问

  1. I2S 的 BCLK、WS 和 DIN/DOUT 分别是什么?
  2. 为什么麦克风是 32-bit slot,上传却是 PCM16?
  3. 16 kHz 能覆盖什么频段?
  4. PCM 和 OPUS 有什么区别?
  5. 为什么音频 buffer 要放 internal memory?
  6. 队列满时丢新包还是旧包?
  7. 如何测端到端延迟?
  8. 如何检测削波?
  9. 3 倍增益如何做饱和?
  10. 完整 AEC、AGC、降噪会放在哪一层?

7. 备用排查案例:启动阶段 internal SRAM 不足

你已经忘记最后采用的修复动作和复测结果,因此当前不能把它讲成完整 STAR 成果。下面内容用于回答内存排查追问,或者帮助你根据日志、Git 和配置重新恢复记忆;在证据补齐前,不主动把它作为“最难 Bug”。

7.1 现象

设备在 app_main() 运行前断言并重启:

1
2
assert failed: esp_startup_start_app app_startup.c:86 (res == pdTRUE)
heap_caps_malloc(0x3200, caps=0x804)

7.2 如何判断阶段

0x3200 = 12800,对应 12288 字节 main task 栈加 512 字节额外栈;0x804 对应 internal 8-bit capability。调用链是创建 main_task 失败,因此 BLE、Wi-Fi、LVGL 页面业务还没开始,不能把根因归到应用运行期。

7.3 根因

内部 SRAM 并非全部可作为普通堆:L2 Cache、静态 IRAM/DATA/BSS、DMA 预留、系统任务栈和组件内存池都会占用。打开 FreeRTOS trace facility 后额外内存把启动阶段推到临界点,导致无法获得满足 capability 的连续内存块。

7.4 为什么“还有 PSRAM”也会失败?

创建 main task 和部分 DMA/驱动对象要求 internal 8-bit 或 DMA-capable 内存,PSRAM 不能无条件替代。判断内存问题不能只看总 free heap,还要看 capability、最大连续块、静态段和申请发生阶段。

7.5 正确排查方式

  • 保留完整断言、backtrace 和申请 capability;
  • 区分 app_main 前后;
  • 查看链接 map 和静态段;
  • 分阶段打印 internal/DMA/PSRAM 的 free、min free、largest block;
  • 保存应用任务 handle,查看已知任务 stack high-water mark;
  • 不盲目扩大所有任务栈;
  • 不随意改变 ESP-Hosted、BLE、SDIO 和 LVGL 初始化顺序。

7.6 这道故事体现什么能力?

不是“会调大内存”,而是能从日志中的大小、capability、启动阶段和内存区域定位系统性资源问题,并排除尚未运行的模块。


8. 核心故事四:导航抖动与到达判断

8.1 抖动原因

  • 直接使用当前短路段方向,OSM 路网小线段角度频繁变化;
  • 相机中心做平滑,但定位点使用另一套实时位置,二者相对漂移;
  • 高频状态和地图几何若同频全量重绘,会加重视觉抖动和 CPU 压力。

8.2 修复思路

  • 使用前方约 38 m 路线点计算前视方向;
  • 相机中心与当前路线位置使用一致锚点;
  • 保留角度平滑;
  • 加小重绘阈值过滤亚像素变化;
  • 40 ms 更新导航状态,地图几何最快 100 ms 更新,低频状态更慢;
  • 绘制时裁剪不可见道路和三角形。

8.3 为什么不能直接平均角度?

角度在 359° 到 1° 之间直接算术平均会得到 180°。应将角度差归一化到 [-180°, 180°],沿最短方向插值,或使用单位向量/复数表示后求方向。

8.4 到达判断为什么容易错?

仅看直线距离会受 GPS 抖动、路线折返和终点附近多路段影响;仅看路线进度可能因投影跳段提前到达。需要组合剩余路线距离、当前位置到终点距离、速度/停留、连续多次判断和迟滞。

8.5 路线算法

仓库的早期内置路网模块使用 Dijkstra,并将图结构放到 PSRAM。当前完整路线也支持手机下发 polyline,再在终端进行坐标转换、累计距离、线段投影、坡度和转向计算。面试时必须区分“终端自己图搜索算路”和“手机下发完整路线”两条路径。


9. BLE 完整路线协议

为什么不用一次 GATT 写完?

路线最多 2048 点,远大于单次 ATT payload,而且连接可能中断。协议设计为:

1
2
3
4
BEGIN  : route_id、版本、点数、总长度、CRC32
DATA : route_id、offset、payload
COMMIT : 请求完整校验与发布
STATUS : 状态、进度、错误码

可靠性如何保证?

  • BEGIN 声明总长度、点数和 CRC;
  • DATA 必须按字节 offset 顺序;
  • COMMIT 时统一检查长度、CRC32、字段和坐标;
  • 只有状态进入 LOADED 才代表业务成功;
  • GATT 写成功只代表链路接收,不代表路线可用;
  • 接收 buffer、点数组和路线任务栈放 PSRAM;
  • NimBLE 回调只做边界检查和投递,不进行地图解析或 LVGL 调用。

CRC32 能防什么?

能检测传输或存储中的随机错误,不能验证数据来源,也不能抵抗恶意篡改。安全通信需要认证和密码学完整性保护。


10. FreeRTOS 与并发追问

项目里有哪些任务?

可按职责回答,不需要背出系统全部任务:

  • LVGL 主任务和 draw worker;
  • NimBLE Host 与 ESP-Hosted/SDIO 系统任务;
  • 姿态融合任务;
  • Wi-Fi 控制任务;
  • AI voice、音频播放和麦克风任务;
  • BLE 路线解析任务;
  • 地图加载/路线准备任务;
  • 按键分发、PMIC IRQ、GNSS 等板级任务。

为什么不让 UI 直接读传感器?

传感器访问可能等待 I2C,算法又有浮点计算;放在 UI 任务会造成动画卡顿。独立任务按数据就绪处理,UI 只读取快照,能隔离硬件延迟和渲染周期。

mutex、临界区和队列如何选?

  • 队列:传递命令、事件或 buffer 所有权;
  • mutex:串行保护软件 I2C 等可能阻塞的共享资源;
  • 临界区:复制固定小快照,必须极短且不阻塞;
  • task notification:ISR 对单个任务的轻量唤醒。

volatile 为什么不够?

它只防止编译器删除访问,不保证复合操作原子、不保护结构体一致性、不处理 cache/多核内存顺序。项目中的快照需要临界区或锁。


11. 内存与性能追问

为什么大量使用 PSRAM?

地图点、路线、UI snapshot、大任务栈和 OPUS 对象占用较大,放 PSRAM 可保留 internal SRAM 给 DMA、ISR、系统任务和低延迟 buffer。但 PSRAM 延迟更高、依赖 cache,也不满足所有 DMA/ISR 访问要求,因此不能把所有对象一律迁走。

为什么要看 largest free block?

总空闲内存足够不代表能满足一次连续分配。任务栈、DMA buffer 或大数组需要连续空间;碎片化时 free_size 很高但申请仍会失败。

40 ms 和 100 ms 应怎样准确表达?

40 ms 是导航状态更新周期,100 ms 是地图几何重建/失效的最短间隔,不是函数执行耗时。项目通过不同频率的定时器、版本变化和局部 invalidate 控制刷新预算。


12. 60 个项目追问清单

架构与职责

  1. 为什么选择 ESP32-P4?
  2. 为什么还要 ESP32-C6?
  3. ESP-Hosted 是什么?
  4. 为什么 P4 跑 Host、C6 跑 Controller?
  5. 为什么使用 SDIO 而不是 SPI?
  6. 项目如何分层?
  7. 你的个人职责是什么?
  8. 哪些模块来自官方例程或第三方库?
  9. 最有挑战的部分是什么?
  10. 如果重做一次会改什么?

传感器和算法

  1. Mahony 与互补滤波有什么关系?
  2. 加速度计、陀螺仪和磁力计各自优缺点?
  3. 为什么需要四元数归一化?
  4. 陀螺零偏如何估计?
  5. 运动时为何不能校准零偏?
  6. 硬铁和软铁干扰是什么?
  7. 为什么要做坐标系变换?
  8. 磁力计坏了如何降级?
  9. Kp/Ki 怎么调?
  10. 如何验证姿态方向没有写反?

音频

  1. PCM、OPUS、I2S 分别是什么?
  2. 为什么选择 16 kHz?
  3. 60 ms 帧的优缺点?
  4. PCM16 mono 一帧多大?
  5. 编解码为什么放独立任务?
  6. 如何处理音频队列满?
  7. 静音门限为什么可能误判?
  8. DTX 是什么?
  9. 如何避免削波?
  10. 当前方案为什么不等于 AEC?

RTOS 与内存

  1. ISR 中做了什么?
  2. 为什么使用 task notification?
  3. 快照如何保证一致性?
  4. PSRAM 和 internal SRAM 怎么选?
  5. DMA buffer 有什么限制?
  6. 任务栈如何估算?
  7. 怎么检测栈溢出?
  8. 为什么 free heap 足够仍申请失败?
  9. 优先级怎么设计?
  10. 如何避免优先级反转?

BLE、地图和协议

  1. GATT Service 与 Characteristic 是什么?
  2. MTU 247 代表什么?
  3. 为什么协议仍应兼容 MTU 23?
  4. 路线为什么需要分包?
  5. CRC32 参数是什么?
  6. 断线后如何恢复传输?
  7. 如何处理重复包?
  8. Dijkstra 的复杂度是什么?
  9. 点到线段投影怎么计算?
  10. 导航角度跨 360° 如何平滑?

调试与验证

  1. 启动断言如何定位?
  2. LVGL 卡顿怎么确定是音频导致?
  3. I2C 没 ACK 怎么查?
  4. BLE 扫描不到怎么查?
  5. 音频无声怎么查?
  6. 姿态漂移怎么查?
  7. 导航提前到达怎么查?
  8. 如何做长时间稳定性测试?
  9. 如何证明优化有效?
  10. 当前项目还有哪些真实边界?

13. 最适合韵音的三个项目故事

Story A:IMU 融合

  • Situation:骑行终端需要稳定姿态和航向,传感器安装方向、零偏和磁干扰导致原始数据不能直接使用。
  • Task:完成驱动、采样、校准、融合和线程安全输出,且不阻塞 UI。
  • Action:软件 I2C、GPIO data-ready、任务通知、统一坐标变换、104 样本零偏、三轴 min/max 磁校准、Mahony 四元数和 NVS 验收保存。
  • Result:实现姿态/磁航向快照和故障降级;实测指标只填写真实验证结果。
  • Evidence:提交 3a8d4b7b10c08fboard_attitude.c

Story B:音频采集、编码与播放链路

  • Situation:终端既要采集语音并上传,也要接收语音导航或对话音频播放,同时不能明显拖慢 LVGL。
  • Task:打通麦克风采集、格式转换、编码上传、下行解码和扬声器播放的数据流,并隔离耗时工作。
  • Action:使用 ICS43434 进行 16 kHz I2S 采集,将 32-bit slot 转成 PCM16 mono;按 60 ms/960 samples 组织帧,调用 OPUS 库编码;下行解码后经 MAX98357A 播放;通过任务和队列隔离回调与耗时处理,并对静音数据做门限和 DTX 控制。
  • Result:完成端到端语音链路和本地语音播放接入。没有保留端到端延迟和长稳测试数据,因此不报具体性能数字。
  • Evidence:app_xiaozhi_controller.c、音频板级代码及提交 3b80c36afb48794b1cd76

Story C:导航抖动

  • Situation:短路段方向跳变、相机与定位点状态不同步导致画面抖动。
  • Task:提高视觉稳定性,同时控制 CPU 和重绘频率。
  • Action:38 m 前视方向、统一锚点、最短角度平滑、亚像素阈值、40/100/500/1000 ms 分级刷新和裁剪。
  • Result:完成对应算法和刷新逻辑修改,运行观察中画面表现有所改善;没有正式前后对比测试,不能声称具体提升比例。
  • Evidence:提交 b10c08f、地图页面和项目文档。

14. 当前边界要主动承认

  • 板载 GNSS 曾未稳定,手机 BLE 定位是当前有效链路之一;
  • Mahony 融合航向尚未正式写入统一运动模型驱动地图;
  • 当前是磁航向,未加入所在地磁偏角得到真北;
  • 运动判断阈值仍需更多骑行场景数据调参;
  • OPUS 算法使用开源库,不是自主编解码器;
  • TTS 播放期间暂停上行不等于完整 AEC;
  • 当前分区表只有 factory app,不应声称已有 A/B OTA;
  • 产品是可运行原型,不是已经量产的商品。

主动说明边界不会减分,反而能证明你理解系统当前状态。


15. 已确认信息和剩余缺口

项目 当前结论
团队规模 3 人
本人职责 独立负责嵌入式端软件实现;具体边界按代码和真实分工回答
项目周期 5 月到 8 月,年份仍需补充
硬件 独立绘制 PCB;板层、关键电路、制板焊接和调试问题仍需补充
比赛 本稿不使用比赛和奖项信息
SRAM 断言 最终修改和复测结果已忘记,不作为主打成果
姿态、音频和长稳指标 没有测试记录,不编数字
导航优化 没有正式前后测试,不讲量化提升

最能代表软件工作的三个文件建议选择:

  1. components/board_peripherals/sensor/board_attitude.c:体现驱动、实时采样、校准、融合、NVS 和并发设计;
  2. bt/app_ble_route.c:体现 GATT 分包协议、状态机、CRC32、PSRAM 和异常处理;
  3. main/ui_app/pages/map_sd_route/page_map_sd_route.c:体现离线地图、导航算法、刷新策略和 LVGL 工程能力。

如果韵音更重视信号处理,可以用 main/app_xiaozhi_controller.c 替换地图文件,重点讲 PCM/OPUS 音频数据流。以上是推荐的讲解入口,不代表其他文件不是你完成的。


16. 最终提醒

面试项目不是功能清单比赛。优先讲清楚一条数据流、一个算法权衡、一个真实问题和一套验证方法。韵音岗位最推荐主讲 IMU 融合,音频链路作为第二故事,BLE 路线协议或地图导航作为工程补充。SRAM 断言只有在重新找回最终修改和复测证据后,才适合作为调试故事。

1